package com.igame.maingame;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import com.igame.maingame.ControlBall.EVENT_TYPE;
import com.igame.maingame.ScreenTextManager.ScreenText;
import com.jme.bounding.BoundingBox;
import com.jme.input.ChaseCamera;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.input.action.InputActionInterface;
import com.jme.input.util.SyntheticButton;
import com.jme.light.Light;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.shape.Box;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.BlendState.DestinationFunction;
import com.jme.scene.state.BlendState.SourceFunction;
import com.jme.system.DisplaySystem;
import com.jme.util.export.xml.XMLImporter;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.PhysicsUpdateCallback;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.contact.ContactInfo;
import com.jmex.physics.material.Material;
import com.jmex.physics.util.SimplePhysicsGame;

public class RollInGraph extends SimplePhysicsGame {
	
	//fill the scene with contents
	String xmltestmodel = "media/models/cubetest-jme.xml";
	DynamicPhysicsNode playerNode;
	private void initScene(){
		initGround();
		initControlBall();
		
		scene.updateGeometricState(0, true);
		scene.updateRenderState();
	}
	
	private String groundfile = "media/models/mazefull-jme.xml";
	private String colnodename = "colnodes";
	String groundDispNodeName = "surface";
	Node ground;
	Vector3f groundScale = new Vector3f(3.0f,1.0f,3.0f);
	private void initGround(){
		URL url = this.getClass().getClassLoader().getResource(groundfile);
		ground = (Node)loadModel(url);
		Node colnode = (Node)ground.getChild(colnodename);
		ground.detachChild(colnode);
		StaticPhysicsNode pn = ReconstructColnode(colnode,Vector3f.UNIT_XYZ);
		ground.attachChild(pn);
		
		Node dispnode = (Node)ground.getChild(groundDispNodeName);
		dispnode.setLocalScale(dispnode.getLocalScale().mult(groundScale));
		
		_initTriggers();
		
//		RenderState rs = display.getRenderer().createWireframeState();
//		rs.setEnabled(true);
//		colnode.setRenderState(rs);
		
//		BlendState bs = display.getRenderer().createBlendState();
//		bs.setSourceFunction(SourceFunction.Zero);
//		bs.setDestinationFunction(DestinationFunction.One);
//		bs.setBlendEnabled(true);
//		bs.setEnabled(true);
//		colnode.setRenderState(bs);
//		colnode.updateRenderState();
		colnode.setCullHint(CullHint.Always);
		
		scene.attachChild(ground);
	}
	
	//Trigger control
	private String bounceBackTriggerName = "bounce_back_triggers";
	String exitTriggerName = "exit_triggers";
	String entranceNodeName = "entrance";
	String itemMarkName = "itemmarks";
	Vector3f entrancePoint = new Vector3f();
	List<Vector3f> itemPoints = new ArrayList<Vector3f>();
	InputHandler triggerHdl;
	private void _initItemMarks(){
		Node node = (Node)ground.getChild(itemMarkName);
		for(Spatial sp:node.getChildren()){
			sp.getLocalTranslation().multLocal(groundScale);
			itemPoints.add(sp.getLocalTranslation().clone());
		}
	}
	private void _initEntrance(){
		Node entranceNode = (Node)ground.getChild(entranceNodeName);
		Spatial ent_obj = (Spatial)entranceNode.getChild(0);
		ent_obj.getLocalTranslation().multLocal(groundScale);
		entrancePoint = ent_obj.getLocalTranslation().clone();
	}
	private void _setTriggers(String triggerNodeName, TriggerAction action, Vector3f scalemore){
		if(triggerHdl == null){
			triggerHdl = new InputHandler();
		}
		Node trnode = (Node)ground.getChild(triggerNodeName);
		//construct physic geometry
		ground.detachChild(trnode);
		StaticPhysicsNode pn = ReconstructColnode(trnode,scalemore);
		ground.attachChild(pn);
		
		//init bounce back triggers
		for(Spatial spn:((Node)pn.getChild(0)).getChildren()){
			StaticPhysicsNode node = (StaticPhysicsNode)spn;
			SyntheticButton sb = node.getCollisionEventHandler();
			triggerHdl.addAction(action, sb, false);
		}
	}
	private void _initTriggers(){
		_initEntrance();
		_initItemMarks();
		_setTriggers(bounceBackTriggerName,new BounceBackTrigger(),new Vector3f(0.75f,1,0.75f));
		_setTriggers(exitTriggerName,new ExitTrigger(),Vector3f.UNIT_XYZ);
		
		PhysicsUpdateCallback puc = new PhysicsUpdateCallback() {
			@Override
			public void beforeStep(PhysicsSpace space, float time) {
			}
			
			@Override
			public void afterStep(PhysicsSpace space, float time) {
				triggerHdl.update(time);
			}
		};
		getPhysicsSpace().addToUpdateCallbacks(puc);
	}
	private abstract class TriggerAction extends InputAction {
		@Override
		public void performAction(InputActionEvent evt) {
			ContactInfo ci = (ContactInfo)evt.getTriggerData();
			Node node1 = ci.getNode1();
			Node node2 = ci.getNode2();

			if(node1 instanceof StaticPhysicsNode && node2 instanceof DynamicPhysicsNode)
				doCollision((StaticPhysicsNode)node1,(DynamicPhysicsNode)node2);
			else if(node2 instanceof StaticPhysicsNode && node1 instanceof DynamicPhysicsNode)
				doCollision((StaticPhysicsNode)node2,(DynamicPhysicsNode)node1);
		}
		
		public abstract void doCollision(StaticPhysicsNode triggerNode, DynamicPhysicsNode objNode);
	}

	private class BounceBackTrigger extends TriggerAction{
		float a = 2000.0f;
		@Override
		public void doCollision(StaticPhysicsNode triggerNode,
				DynamicPhysicsNode objNode) {
			Object objtype = objNode.getUserData(OBJECT_TYPE.class.getName());
			if(objtype != null && ((OBJECT_TYPE)objtype)==OBJECT_TYPE.CONTROL_BALL){
				ControlBall cb = (ControlBall)objNode.getChild(0);
				Vector3f facedir = cb.getFaceDirection();
				objNode.addForce(facedir.mult(-objNode.getMass()*a));
				cb.addMark(penalizeMark);
			}
		}
	}
	private class ExitTrigger extends TriggerAction{
		@Override
		public void doCollision(StaticPhysicsNode triggerNode,
				DynamicPhysicsNode objNode) {
			Object type = objNode.getUserData(OBJECT_TYPE.class.getName());
			if(type instanceof OBJECT_TYPE){
				OBJECT_TYPE objtype = (OBJECT_TYPE)type;
				if(objtype == OBJECT_TYPE.SOLDIER_BALL){
					System.out.printf("destroy %s\n",objNode.getName());
					playerBall.addMark(itemExitMark);
					sballMgr.destroyBall(objNode.getName());
				}
			}
		}
		
	}
	
	private StaticPhysicsNode ReconstructColnode( Node colnode, Vector3f scalemore ){
		//only one level tree is assumed
		colnode.removeFromParent();
		Quaternion unitquat = new Quaternion();
		unitquat.loadIdentity();
		List<Spatial> splist = new ArrayList<Spatial>();
		splist.addAll(colnode.getChildren());
		Vector3f scale = groundScale.clone();
		Vector3f invscalemore = new Vector3f(1/scalemore.x,1/scalemore.y,1/scalemore.z);

		for(int i=0;i<splist.size();i++){
			Node thisp = (Node)splist.get(i);
			colnode.detachChild(thisp);
			
			Quaternion orgrot = thisp.getLocalRotation().clone();
			Vector3f orgloc = thisp.getLocalTranslation().clone().mult(scale);
			Vector3f orgscale = thisp.getLocalScale().clone().mult(scale);
			
			thisp.setLocalRotation(unitquat);
			thisp.setLocalTranslation(0, 0, 0);
			thisp.setLocalScale(1.0f);
			
			StaticPhysicsNode spn = getPhysicsSpace().createStaticNode();
			spn.setMaterial(Material.CONCRETE);
			spn.attachChild(thisp);
			spn.generatePhysicsGeometry();
			
			spn.setLocalTranslation(orgloc);
			//thisp.setLocalTranslation(orgloc);
			
			spn.setLocalRotation(orgrot);
			//thisp.setLocalRotation(orgrot);
			
			spn.setLocalScale(orgscale.mult(scalemore));
			thisp.setLocalScale(invscalemore);
			
			spn.setName(thisp.getName());
			colnode.attachChild(spn);
		}
		StaticPhysicsNode pn = getPhysicsSpace().createStaticNode();
//		pn.setLocalRotation(colnode.getLocalRotation());
//		pn.setLocalTranslation(colnode.getLocalTranslation());
//		pn.setLocalScale(colnode.getLocalScale());
//		//pn.setLocalRotation(colnode.getLocalRotation());
//		colnode.setLocalRotation(new Quaternion().fromAngles(0, 0, 0));
//		colnode.setLocalScale(pn.getLocalScale());
//		colnode.setLocalTranslation(0, 0, 0);
		pn.attachChild(colnode);
		//pn.generatePhysicsGeometry();
		return pn;
	}
	
	private Spatial loadModel(URL modelURL){
		Spatial loadedspatial = null;
		XMLImporter xml = XMLImporter.getInstance(); 
		try{
			loadedspatial = (Spatial)xml.load(modelURL);
			loadedspatial.setModelBound(new BoundingBox());
			loadedspatial.updateModelBound();
		}catch(Exception e){
			throw new IllegalArgumentException("fail to load xml");
		};
		loadedspatial.setModelBound(new BoundingBox());
		loadedspatial.updateModelBound();
		return loadedspatial;
	}
	
	private ChaseCamera chasecam;
	private InputHandler cbinput;
	private Node cbnode;
	private void initControlBox(){
		//create a non-physic box and control it
		Box smallbox = new Box("controlbox", Vector3f.ZERO, 3, 3, 1);
		cbnode = new Node("controlboxnode");
		cbnode.setLocalTranslation(0, 10,0);
		cbnode.attachChild(smallbox);
		scene.attachChild(cbnode);
		
		//build control logic
		cbinput = new InputHandler();
		//KeyBindingManager kbm = KeyBindingManager.getKeyBindingManager();
		InputAction tempia = new InputAction(){
			public void performAction(InputActionEvent evt) {
				// TODO Auto-generated method stub
				if(evt.getTriggerPressed()){
					DynamicPhysicsNode ballnode = (DynamicPhysicsNode)scene.getChild("ballnode");
					ballnode.addForce(Vector3f.UNIT_X.mult(4));
				}
			}
		};
		//cbinput.addAction(tempia, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_U, InputHandler.AXIS_NONE, true);
		cbinput.setEnabled(true);
		
		//build chase camera
		physicInputHandler = new InputHandler();
		PhysicsUpdateCallback puc = new PhysicsUpdateCallback(){
			public void afterStep(PhysicsSpace space, float time) {
				physicInputHandler.update(time);
			}
			public void beforeStep(PhysicsSpace space, float time) {
			}
		};
		getPhysicsSpace().addToUpdateCallbacks(puc);
		
		//physic input action
		InputAction physicInputAction = new InputAction() {
			public void performAction(InputActionEvent evt) {
				DynamicPhysicsNode node = (DynamicPhysicsNode)scene.getChild("ballnode");
				float forcescale = 4;
				node.addForce(Vector3f.UNIT_X.mult(forcescale));
			}
		};
		physicInputHandler.addAction(physicInputAction, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_I, InputHandler.AXIS_NONE, true);
	}
	InputHandler physicInputHandler;
	
	private Node scene;
	Text txCamPos = new Text("txcam","camera pos");
	private void initCameraText(){
//		txCamPos.setRenderState(Text.getDefaultFontTextureState());
//		txCamPos.setRenderState(Text.getFontBlend());
//		BlendState rs = Text.getFontBlend();
//		rs.setDestinationFunction(DestinationFunction.Zero);
//		txCamPos.setRenderState(rs);
//		txCamPos.setTextColor(new ColorRGBA(1,0,0,1));
//		graphNode.attachChild(txCamPos);
//		graphNode.updateRenderState();
	}

	@Override
	protected void simpleUpdate(){
//		StringBuffer sb = txCamPos.getText();
//		Vector3f pos = cam.getLocation().clone();
//		String str = String.format("x=%.3f,y=%.3f,z=%.3f", pos.x,pos.y,pos.z);
//		sb.replace(0, sb.length(), str);
		checkTimeUp();
		//check command
		if(KeyBindingManager.getKeyBindingManager().isValidCommand(cmdAddSball,false)){
			sballMgr.addBall(playerNode.getLocalTranslation().add(0, 10, 0),playerBall);
		}
		
		if(KeyBindingManager.getKeyBindingManager().isValidCommand(cmdAddBItem,false)){
			for(Vector3f pos:itemPoints){
				bitemMgr.addItem(pos);
			}
		}
		
		if(KeyBindingManager.getKeyBindingManager().isValidCommand(cmdGameReset,false)){
			resetGame();
		}
		
		if(KeyBindingManager.getKeyBindingManager().isValidCommand(cmdGameRun,false)){
			startGame();
		}
		
		updateChaseCamera(playerCamNode,playerBall);
		
		//display control ball information
//		Quaternion quat = playerBall.getParent().getLocalRotation().clone();
//		float[] angles = new float[3];
//		quat.toAngles(angles);
//		String rotinfo = String.format("yaw=%.2f,roll=%.2f,pitch=%.2f", angles[0],angles[1],angles[2]);
//		txNodeOrientation.setString(rotinfo);
	}
	
	//player control
	private class BallMoveControl implements InputActionInterface{
		BallExertForce forceapplier;
		ControlBall target;
		boolean forward = true;
		public BallMoveControl( ControlBall cb , boolean forward){
			target = cb;
			forceapplier = new BallExertForce(cb.getFaceDirection(),(DynamicPhysicsNode)cb.getParent());
			this.forward = forward;
		}
		public void performAction(InputActionEvent evt) {
			forceapplier.setDirection(target.getFaceDirection().mult(forward?1:-1));
			forceapplier.performAction(evt);
		}
	}
	private class BallBreakControl implements InputActionInterface{
		BallExertForce forceapplier;
		ControlBall target;
		DynamicPhysicsNode node;
		public BallBreakControl( ControlBall cb ){
			target = cb;
			forceapplier = new BallExertForce(cb.getFaceDirection(),(DynamicPhysicsNode)cb.getParent());
			node = (DynamicPhysicsNode)cb.getParent();
		}
		public void performAction(InputActionEvent evt) {
			Vector3f vdir = new Vector3f();
//			node.getLinearVelocity(vdir);
//			if(vdir.dot(target.getFaceDirection())<=0)
//				return;
//			System.out.println(vdir.toString());
//			vdir.y = 0;
//			vdir.normalizeLocal();
			node.getAngularVelocity(vdir);
			node.addTorque(vdir.mult(-1 * 5));
//			forceapplier.setDirection(vdir.mult(-1));
//			forceapplier.performAction(evt);
		}
	}
	private class BallTurnControl implements InputActionInterface{
		ControlBall target;
		DynamicPhysicsNode node;
		//BallExertForce forceapplier;
		float turnspeed = 1.0f;
		boolean turnleft = true;
		float mass;
		float radius = 7.0f;
		float radiusForceScale = 0.5f;
		public BallTurnControl(ControlBall cb, boolean turnleft){
			target = cb;
			node = (DynamicPhysicsNode)cb.getParent();
			//forceapplier = new BallExertForce(cb.getFaceDirection(),node);
			//forceapplier.setStaticScale(0.1f);
			this.turnleft = turnleft;
			mass = node.getMass();
		}
		@Override
		public void performAction(InputActionEvent evt) {
			applyCenterForce();
			if(turnleft)
				target.addFaceAngle(evt.getTime() * turnspeed);
			else
				target.addFaceAngle(-evt.getTime() * turnspeed);
		}
		
		public void applyCenterForce(){
			Vector3f dir = target.getFaceDirection();
			Vector3f pdr = new Vector3f(-dir.z,0,dir.x);
			if(turnleft)
				pdr.multLocal(-1);
			Vector3f voc = new Vector3f();
			node.getLinearVelocity(voc);
			if(dir.dot(voc)<0)
				pdr.multLocal(-1);
			float speed = voc.lengthSquared();
			float r = speed/turnspeed/50;
			float forcescale = mass * turnspeed * turnspeed * r;
			node.addForce(pdr.mult(forcescale));
		}
	}
	private class BallExertForce implements InputActionInterface {
		private Vector3f forcedir = Vector3f.ZERO.clone();
		private DynamicPhysicsNode ballnode;
		private float staticscale = 1;
		private float posforcescale = 1;
		private float negforcescale = 0.7f;
		private Vector3f gravity = new Vector3f(0,-9.8f,0);
		private float ballmass = 0;
		private float power = 2.5f;
		public BallExertForce(Vector3f dir, DynamicPhysicsNode node){
			forcedir = dir.clone().normalize();
			ballnode = node;
			posforcescale *= staticscale;
			negforcescale *= staticscale;
			ballnode.getSpace().getDirectionalGravity(gravity);
			gravity = gravity.clone();
			ballmass = ballnode.getMass();
		}
		public void setDirection( Vector3f dir ){
			forcedir = dir.clone();
		}
		@Override
		public void performAction(InputActionEvent evt) {
			Vector3f vel = new Vector3f(0,0,0);
			float normvel = vel.lengthSquared();
			float thisforcescale = 0;
			if(normvel < 0.1)
				thisforcescale = power * 10;
			else
				thisforcescale = power/normvel;
			ballnode.getLinearVelocity(vel);
			//float thisangle = last_angle + 
			Vector3f thisforce;// = gravity.mult(ballmass * Math.sin(a)
			if( vel.dot(forcedir) >= 0)
				thisforce = forcedir.mult(posforcescale * thisforcescale);
			else
				thisforce = forcedir.mult(negforcescale * thisforcescale);
			ballnode.addForce(thisforce);
		}
		public void setStaticScale( float newscale ){
			staticscale = newscale;
		}
	}
	
	InputHandler ballhdl;
	BallExertForce ballact;
	ControlBall playerBall;
	private void initControlBall(){
		playerBall = new ControlBall("ball",50,50,0.5f);
//		playerBall.setModelBound()
//		playerBall.updateModelBound();
		DynamicPhysicsNode ballnode = this.getPhysicsSpace().createDynamicNode();
		ballnode.setUserData(OBJECT_TYPE.class.getName(),OBJECT_TYPE.CONTROL_BALL);
		ballnode.setUserData(ControlBall.getControlBallTag(), playerBall);
		ballnode.attachChild(playerBall);
		ballnode.generatePhysicsGeometry();
		ballnode.setLocalTranslation(entrancePoint);
		ballnode.setMaterial(Material.WOOD);
		scene.attachChild(ballnode);
		
		this.playerNode = ballnode;
		
		ballhdl = new InputHandler();
		PhysicsUpdateCallback puc = new PhysicsUpdateCallback(){
			public void afterStep(PhysicsSpace space, float time) {
				ballhdl.update(time);
				
				//Angular Damper
				Vector3f anglespeed = new Vector3f();
				playerNode.getAngularVelocity(anglespeed);
				float speedscale = anglespeed.length();
				playerNode.addTorque(anglespeed.normalize().negate().mult(speedscale<0.5?(speedscale*4):6.0f*playerNode.getMass()));
			}
			public void beforeStep(PhysicsSpace space, float time) {
			}
		};
		InputActionInterface act;
		getPhysicsSpace().addToUpdateCallbacks(puc);
		act = new BallMoveControl(playerBall, true);
		ballhdl.addAction(act, InputHandler.DEVICE_KEYBOARD, 
				KeyInput.KEY_W, InputHandler.AXIS_NONE, true);
		act = new BallMoveControl(playerBall, false);
		ballhdl.addAction(act, InputHandler.DEVICE_KEYBOARD, 
				KeyInput.KEY_S, InputHandler.AXIS_NONE, true);
		act = new BallBreakControl(playerBall);
		ballhdl.addAction(act, InputHandler.DEVICE_KEYBOARD, 
				KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, true);
		act = new BallTurnControl(playerBall, true);
		ballhdl.addAction(act, InputHandler.DEVICE_KEYBOARD, 
				KeyInput.KEY_A, InputHandler.AXIS_NONE, true);
		act = new BallTurnControl(playerBall, false);
		ballhdl.addAction(act, InputHandler.DEVICE_KEYBOARD, 
				KeyInput.KEY_D, InputHandler.AXIS_NONE, true);
//		ballact = new BallExertForce(new Vector3f(0,0,-1),ballnode);
//		ballhdl.addAction(ballact, InputHandler.DEVICE_KEYBOARD, 
//				KeyInput.KEY_U, InputHandler.AXIS_NONE, true);
//		ballact = new BallExertForce(new Vector3f(-1,0,0),ballnode);
//		ballhdl.addAction(ballact, InputHandler.DEVICE_KEYBOARD, 
//				KeyInput.KEY_H, InputHandler.AXIS_NONE, true);
//		ballact = new BallExertForce(new Vector3f(1,0,0),ballnode);
//		ballhdl.addAction(ballact, InputHandler.DEVICE_KEYBOARD, 
//				KeyInput.KEY_K, InputHandler.AXIS_NONE, true);
//		ballact = new BallExertForce(new Vector3f(0,0,1),ballnode);
//		ballhdl.addAction(ballact, InputHandler.DEVICE_KEYBOARD, 
//				KeyInput.KEY_J, InputHandler.AXIS_NONE, true);
		
		playerBall.addCallback(EVENT_TYPE.MARK_CHANGED, new ControlBall.ControlBallEventCallback() {
			
			@Override
			public void performAction(ControlBall source) {
				txMark.setString(_txMark(source.getMark(),maxMark));
				if(source.getMark() >= maxMark){
					winGame(source);
				}else if(source.getMark() <= minMark){
					loseGame(source);
				}
				int dm = source.getMarkRecentChange();
				deltaMark += dm;
				if(deltaMark >= bonusMark)
				{
					deltaMark -= bonusMark;
					sballMgr.addBall(source.getParent().getLocalTranslation().add(0, 10, 0), source);
				}else if(deltaMark < 0){
					SoldierBallManager.SoldierBall sb= sballMgr.getOneBall();
					if(sb!=null)
					{
						sballMgr.destroyBall(sballMgr.getOneBall().getName());
					}
					deltaMark = 0;
				}
				txDeltaMark.setString(_txBonusMark(deltaMark,bonusMark));
			}
		});
		
		_initChaseCamera();
	}
	
	Camera playerCam;
	CameraNode playerCamNode;
	Vector3f chaseCamTargetOffset = new Vector3f(0,3,7);
	float lookOffset = 3.0f;
	private void _initChaseCamera(){
		int width = display.getWidth();
		int height = display.getHeight();
		Vector3f loc = new Vector3f(0.0f, 0.0f, 0.0f);
		Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
		Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
		Vector3f dir = new Vector3f(0.0f, 0f, -1.0f);

		playerCam = display.getRenderer().createCamera(width, height);
		playerCam.setFrustumPerspective(45.0f, (float)width / (float)height, 1, 1000);
		playerCam.setFrame(loc, left, up, dir);
		playerCam.update();
		
		playerCamNode = new CameraNode("camnode",playerCam);
		display.getRenderer().setCamera(playerCam);
		scene.attachChild(playerCamNode);
	}
	private void updateChaseCamera( CameraNode camnode, ControlBall ball ){
		//let the camera follow the control ball
		Vector3f ballface = ball.getFaceDirection();
		float yoff = chaseCamTargetOffset.y;
		float zoff = chaseCamTargetOffset.z; 
		Vector3f offset = ballface.negate().mult(zoff).addLocal(0, yoff, 0);
		camnode.setLocalTranslation(ball.getWorldTranslation().add(offset));
		
		Vector3f lookpos = ball.getWorldTranslation().clone();
		lookpos.addLocal(ballface.mult(lookOffset));
		camnode.lookAt(lookpos, Vector3f.UNIT_Y);
	}
	
	//item control
	private SoldierBallManager sballMgr;
	private String cmdAddSball = "addsball";
	private void initSBallControl(){
		sballMgr = new SoldierBallManager(scene, getPhysicsSpace());
		KeyBindingManager.getKeyBindingManager().add(cmdAddSball, KeyInput.KEY_Y);
		PhysicsUpdateCallback puc = new PhysicsUpdateCallback() {
			@Override
			public void beforeStep(PhysicsSpace space, float time) {
				// TODO Auto-generated method stub
				
			}
			@Override
			public void afterStep(PhysicsSpace space, float time) {
				sballMgr.updateForce(playerNode.getLocalTranslation());
			}
		};
		getPhysicsSpace().addToUpdateCallbacks(puc);
	}
	
	private BounusItemManager bitemMgr;
	private String cmdAddBItem = "addBItem";
	private void initBItemControl(){
		bitemMgr = new BounusItemManager(scene,getPhysicsSpace());
		KeyBindingManager.getKeyBindingManager().add(cmdAddBItem, KeyInput.KEY_I);
		BounusItemManager.ItemPickedContactCallback ipcc = bitemMgr.new ItemPickedContactCallback ();
		getPhysicsSpace().getContactCallbacks().add(ipcc);
	}
	
	//screen text
	ScreenTextManager textMgr;
	ScreenTextManager.ScreenTextNode nodeTxWin;
	ScreenTextManager.ScreenTextNode nodeTxReady;
	ScreenTextManager.ScreenTextNode nodeTxMark;
	ScreenTextManager.ScreenTextNode nodeTxLose;
	ScreenTextManager.ScreenTextNode nodeTxNotice;
	ScreenTextManager.ScreenText txMark;
	ScreenTextManager.ScreenText txDeltaMark;
	ScreenTextManager.ScreenText txTimer;
	void _genTxEachLine(ScreenTextManager.ScreenTextNode node, String str){
		int curidx = 0;
		int lastidx = 0;
		int height = display.getHeight();
		int width = display.getWidth();
		
		float scale = 1.0f;
		float dh = 17 * scale;
		float th = 50 * scale;
		List<ScreenTextManager.ScreenText> list= new ArrayList<ScreenTextManager.ScreenText>();
		while((curidx = str.indexOf('\n', lastidx)) > 0){
			String thistr = str.substring(lastidx, curidx);
			ScreenTextManager.ScreenText stx = textMgr.createText(node, thistr);
			list.add(stx);
			lastidx = curidx + 1;
		}
		int i =0;
		for(ScreenTextManager.ScreenText stx:list){
			stx.setLocalScale(scale);
			stx.setLocalTranslation(width/2 - stx.getWidth()/2, height - th - i*dh, 0);
			i++;
		}
	}
	String _txWin(){
		return "You Win!";
	}
	String _txNotice(){
		String notice = String.format(
				"Your goal is to collect the cubes\n\n" +
				"--------------Awarding Guideline-------------\n" + 
				"For each cube collected, you are awarded 10 marks\n" +
				"Once you get %d marks, you win\n" +
				"You get special award by continuously gaining marks\n" +
				"Each time you pick up a cube, you gain some Bonus Mark,\n" +
				"you are awarded a helper ball once you bonus mark reaches %d.\n" +
				"The helper ball drops from right above you.\n" + 
				"The helper ball will follow you and pick up cubes once it\n" +
				"touches them.\n" + 
				"There is a red pillar in this level, into which you can lead\n" +
				"the helper balls in order to get extra marks\n" +
				"--------------Penalizing Rules---------------\n" +
				"Try not to touch the gray pillows in the level\n" + 
				"Each time you touch it, %d marks will be gone\n" +
				"Once your total marks get below %d, you lose\n" +
				"Each time you touch those dangerous pillars,\n"+ 
				"you lose some Bonus Mark.\n" +
				"If you don't have enough Bouns Mark to lose,\n" +
				"a helper ball will be taken out.\n" +
				"Also note that you only have %.3f secs to finish your task.\n\n" +
				"PRESS 1 to reset, 2 to start\n" + 
				"WASD to control movement and view, SPACE to break\n",
				maxMark,bonusMark,-2*penalizeMark,minMark,timeLimit);
		return notice;
	}
	String _txReady(){
		return "Cube Collector";
	}
	String _txMark( int mark, int winmark ){
		return String.format("Mark = %d/%d", mark,winmark);
	}
	String _txBonusMark(int mark, int winmark){
		return String.format("Bonus Mark = %d/%d", mark,winmark);
	}
	String _txTimeLeft(float secs){
		return String.format("Time Left = \n%.3f", secs);
	}
	String _txLose(){
		return "You lose...";
	}
	private void initScreenText(){
		textMgr = new ScreenTextManager(graphNode);
		nodeTxWin = textMgr.createTextNode();
		ScreenTextManager.ScreenText stx;
		stx = textMgr.createText(nodeTxWin, _txWin());
		stx.setLocalScale(3.0f);
		stx.setToCenter();
		nodeTxReady = textMgr.createTextNode();
		stx = textMgr.createText(nodeTxReady, _txReady());
		stx.setLocalScale(1.5f);
		stx.setToCenter();
		nodeTxReady.hide();
		nodeTxMark = textMgr.createTextNode();
		txMark = textMgr.createText(nodeTxMark, _txMark(beginMark,maxMark));
		txDeltaMark = textMgr.createText(nodeTxMark, _txBonusMark(deltaMark,bonusMark));
		txTimer = textMgr.createText(nodeTxMark, _txTimeLeft(timeLimit) );
		txTimer.setToCenter();
		txTimer.getLocalTranslation().addLocal(0, 0.3f*display.getHeight(), 0);
		int width = display.getWidth();
		int height = display.getHeight();
		Vector3f markpos = new Vector3f( width - 40 - txMark.getWidth(), 5 , 0 );
		txMark.setLocalTranslation(markpos);
		txDeltaMark.setLocalTranslation(markpos.add(-20, txMark.getHeight()+4, 0));
		nodeTxLose = textMgr.createTextNode();
		stx = textMgr.createText(nodeTxLose, _txLose());
		stx.setLocalScale(3.0f);
		stx.setToCenter(); 
		nodeTxNotice = textMgr.createTextNode();
		_genTxEachLine(nodeTxNotice, _txNotice());
		nodeTxReady = nodeTxNotice;
		
		initDebugScreenText();
	}
	ScreenTextManager.ScreenText txNodeOrientation;
	private void initDebugScreenText(){
		ScreenTextManager.ScreenTextNode node = textMgr.createTextNode();
		txNodeOrientation = textMgr.createText(node, "");
		txNodeOrientation.setLocalTranslation(0, 0, 0);
		node.show();
	}

	
	//Game state control
	enum GameState{
		READY,
		RUNNING,
		STOP
	}
	GameState globalState = GameState.READY;
	String cmdGameRun = "game_run";
	String cmdGameReset = "game_reset";
	FirstPersonHandler mainInput;
	float def_player_height = 10;
	Vector3f def_cam_pos = new Vector3f(0,56,100);
	float timeLimit = defTimeLimit;
	float timeLeft = defTimeLimit;
	static final float defTimeLimit = 60 * 3.0f;
	private void checkTimeUp(){
		if(globalState == GameState.RUNNING)
		{
			timeLeft = timeLimit - timer.getTimeInSeconds();
			txTimer.setString(String.format("Time Left = %.3f",timeLeft));
			if(timeLeft < 0)
				loseGame(playerBall);
		}
	}
	private void initGlobalControl(){
		KeyBindingManager.getKeyBindingManager().add(cmdGameRun, KeyInput.KEY_2);
		KeyBindingManager.getKeyBindingManager().add(cmdGameReset, KeyInput.KEY_1);
	}
	
	//Game reset
	private void resetGame(){
		_resetPlayer();
		_resetCamera();
		_resetScreenText();
		_resetScene();
		globalState = GameState.READY;
	}
	private void _resetPlayer(){
		ballhdl.setEnabled(false); //block input when the game is being reset
		playerNode.setLinearVelocity(Vector3f.ZERO);
		playerNode.setLocalTranslation(entrancePoint.clone());
		playerNode.setAngularVelocity(Vector3f.ZERO);
		playerNode.setActive(false);
		playerBall.setCallbackEnable(false);
		playerBall.setMark(beginMark);
		playerBall.setCallbackEnable(true);
		
		deltaMark = 0;
		timer.reset();
		timeLeft = timeLimit;
		txTimer.setString(_txTimeLeft(timeLimit));
	}
	private void _resetScreenText(){
		nodeTxReady.block();
		nodeTxWin.hide();
		nodeTxLose.hide();
		txMark.setString(_txMark(beginMark,maxMark));
		txDeltaMark.setString(_txBonusMark(deltaMark, bonusMark));
	}
	private void _resetCamera(){
		cam.setLocation(def_cam_pos.clone());
		cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
		cam.update();
	}
	private void _resetScene(){
		sballMgr.clear();
		bitemMgr.clear();
	}
	
	//Game start
	private void startGame(){
		_startScreenText();
		_startPlayer();
		for(Vector3f pos:itemPoints){
			bitemMgr.addItem(pos);
		}
		globalState = GameState.RUNNING;
	}
	private void _startScreenText(){
		nodeTxWin.hide();
		nodeTxReady.hide();
	}
	private void _startPlayer(){
		playerNode.setLinearVelocity(Vector3f.ZERO);
		playerNode.setActive(true);
		//mainInput.setEnabled(true);
		ballhdl.setEnabled(true);
		timer.reset();
	}
	
	//Game ended
	int maxMark = 600;
	int minMark = 0;
	int beginMark = 200;
	int deltaMark = 0;
	int bonusMark = 50;
	int itemExitMark = 30;
	int penalizeMark = -30;
	
	private void winGame( ControlBall player ){
		nodeTxWin.block();
		globalState = GameState.STOP;
		//mainInput.setEnabled(false);
	}
	private void loseGame( ControlBall player ){
		nodeTxLose.block();
		globalState = GameState.STOP;
	}
	
	@Override
	protected void simpleInitGame() {
		// TODO Auto-generated method stub
		scene = this.rootNode;
		mainInput = (FirstPersonHandler)cameraInputHandler;
		mainInput.getKeyboardLookHandler().setMoveSpeed(20);
		input.setEnabled(false);
		
		//getPhysicsSpace().setDirectionalGravity(Vector3f.UNIT_Y.mult(-9.8f));
		initScene();
		initCameraText();
		initSBallControl();
		initBItemControl();
		initScreenText();
		initGlobalControl();
		resetGame();
		
        ballhdl.addAction( new InputAction() {
            public void performAction( InputActionEvent evt ) {
                if ( evt.getTriggerPressed() ) {
                    showPhysics = !showPhysics;
                }
            }
        }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_V, InputHandler.AXIS_NONE, false );
		
		Light scenelight = this.lightState.get(0);
		scenelight.setAmbient(new ColorRGBA(1,1,1,1));
		scenelight.setDiffuse(new ColorRGBA(1,1,1,1));
		scenelight.setAttenuate(false);
		scenelight.setSpecular(new ColorRGBA(1,1,1,1));
		
		//this.lightState.get(0).setEnabled(true);
		scene.updateGeometricState(0, true);
		scene.updateRenderState();
	}
}
















